/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.speechacts;

import java.util.Arrays;
import java.util.LinkedList;

import robocup.component.geometry.Vektor;
import robocup.component.math.NumberConverter;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * This class provides methods for encoding and decoding of messages. Encoding
 * enables to create strings out of objects such as of a Player or the Ball. 
 * Decoding works the other way round, whereas the first character of the string
 * is interpreted as message-id.<br>
 * The set of allowed characters is given in NumberConverter.digits, together
 * with some useful methods to transform decimal numbers into numbers of base
 * digits.length. For the purpose of better performance, some arrays containing
 * pre-calculated values are also given here.<br>
 * Note that for floating-point calculations, all values are normalized and 
 * non-negative. This implies that positions have to encoded by angles of the
 * interval [0,360] normalized to [0,1] and with distances reaching to an upper 
 * limit [0,70], also normalized. This works similar for other encodings.<br><br>
 * 
 * Use this class as follows:
 * <ol>
 *   <li> For encoding, simply call the corresponding method to create a string
 *        out of the given data. These strings can be given to a say-message
 *        directly. You can also use more than one encoded string by 
 *        concatenation, but be sure that the length of the resulting string
 *        does not exceed the maximum number of allowed characters.
 *   <li> For decoding, use the method <code>generateMessages()</code> first,
 *        which extracts all messages contained in a string and remembers their
 *        ids in <code>this.messageTypes</code>.
 *   <li> After that, iterate the message-types (<code>getMessageTypes()</code>
 *        returns them), and if the actual message should be decoded, call the
 *        method <code>decodeMessageNumber()</code> by giving the index of the
 *        type as parameter. This will result in decoding the corresponding 
 *        message, and sets the specific attributes of this class.
 *   <li> Use the getter methods (e.g. getSpeed) to access the decoded values.
 *  </ol> 
 * 
 * Note also that using this class should be done in a certain way. Do not 
 * create multiple instances of this class, but use that instantiated in the 
 * WorldModel (should be available in most classes). After decoding, the 
 * resulting values (e.g. the speed of a player) is bound to an attribute of 
 * this class (e.g. this.speed), and can be read by using the corresponding 
 * getter-method. Be sure that the used decoding sets the specific values you
 * read afterwards, because this class doesn't check, which are actually filled.
 * Otherwise you might use old values, which do not belong to the message you
 * decoded last.
 */
public class MessageFactory {

    /**
     * Cycle in which the last messages was received.
     */
    private int bodyCycle = -1;
    /**
     * This array stores the values of each character from <code>
     * NumberConverter.digits</code>, whereas each value is located at the index
     * of the characters integer value (for fast access). Note that the
     * characters are ordered by their int-value, such that the last character,
     * which is used to initialize the array, also has the largest value.
     */
    public final int[] valuesOfChars = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1];
    /**
     * This array stores the lengths of each message, which is identified by the
     * first character of the corresponding string. The lengths of the messages
     * are also accessed via the integer value of the id as index. Up to now,
     * this method allows to define digits.length different messages. If more
     * are required, some id's should introduce a following id, which on the
     * other hand reduces the available characters for encoding.
     */
    public final int[] lengthOfMessage = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1];
    /**
     * This array contains precalculated values for the purpose of using the
     * information of one character for two information. The given array is used
     * to access the first (higher) value. An example could be the encoding of
     * the tricot number (11 different values) and the stamina in 4 different
     * levels. Since the former can be expressed using 4 bits and the latter
     * using 2 bits, they can share a character (little more than 6 bits) for
     * the encoding. The frontValue refers to the larger bits.
     */
    public final int[][] sharedFrontValue = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1][8];
    /**
     * This array contains precalculated values for the purpose of using the
     * information of one character for two information. The given array is used
     * to access the first (higher) value. An example could be the encoding of
     * the tricot number (11 different values) and the stamina in 4 different
     * levels. Since the former can be expressed using 4 bits and the latter
     * using 2 bits, they can share a character (little more than 6 bits) for
     * the encoding. The backValue refers to the lower bits.
     */
    public final int[][] sharedBackValue = new int[((int) NumberConverter.digits[NumberConverter.digits.length - 1]) + 1][8];
    /**
     * A string buffer for performant string concatenation. This is used for
     * creating the messages.
     */
    private StringBuffer str = new StringBuffer();
    /**
     * A list containing all currently received message types. The message
     * factory first retrieves all types before decoding all messages, which
     * enables to decode only those messages, which are currently needed.
     */
    private LinkedList<MESSAGES> messageTypes = new LinkedList<MESSAGES>();
    /**
     * A list containing the corresponding message contents for the <code>
     * messageTypes</code>. These are created by splitting received messages.
     */
    private LinkedList<String> messageContent = new LinkedList<String>();
    /**
     * Buffer-array for a single double value. Used with the class <code>
     * NumberConverter</code> in order to transform doubles to a string and
     * vice versa.
     */
    private final double[] data_1 = new double[1];
    /**
     * Buffer-array for two double values. Used with the class <code>
     * NumberConverter</code> in order to transform doubles to a string and
     * vice versa, whereas this may be used to transform two doubles into
     * three characters in order to gain higher precision.
     */
    private final double[] data_2 = new double[2];
    /**
     * Buffer-array for a single character. Used with the class <code>
     * NumberConverter</code> in order to transform characters to doubles and
     * vice versa.
     */
    private final char[] cdata_1 = new char[1];
    /**
     * Buffer-array for two characters. Used with the class <code>
     * NumberConverter</code> in order to transform characters to doubles and
     * vice versa.
     */
    private final char[] cdata_2 = new char[2];
    /**
     * Buffer-array for three characters. Used with the class <code>
     * NumberConverter</code> in order to transform characters to doubles and
     * vice versa.
     */
    private final char[] cdata_3 = new char[3];
    /**
     * Buffer-array for four characters. Used with the class <code>
     * NumberConverter</code> in order to transform characters to doubles and
     * vice versa.
     */
    private final char[] cdata_4 = new char[4];
    /**
     * Maximum value for angles. These are encoded using the [0,360] interval,
     * because no negative values can be encoded.
     */
    private final double maxAngle = 360;
    /**
     * Maximum values for distances. Since all values with floating points are
     * normalized, a maximum value has to be given.
     */
    private final double maxPosDist = 70;
    /**
     * The maximum encoded speed of players.
     */
    private final double maxPlayerSpdDist = 1.2;
    /**
     * The maximum encoded speed of the ball.
     */
    private final double maxBallSpdDist = 2.7;
    /**
     * The maximum encoded stamina value.
     */
    private final int maxStamina = 4000;
    /**
     * After decoding the message DRIBBLE_PPOS, the corresponding positions
     * of the players are stored in this array.
     */
    private Vektor[] positions = {new Vektor(), new Vektor()};
    /**
     * After decoding the message DRIBBLE_PPOS, the corresponding numbers
     * of the players are stored in this array.
     */
    private int[] numbers = {-1, -1};
    /**
     * Every decoded position is stored in this attribute.
     */
    private Vektor position = new Vektor();
    /**
     * Every decoded speed is stored in this attribute.
     */
    private Vektor speed = new Vektor();
    /**
     * A number of a player is stored in this attribute. Numbers reaching from
     * 1 to 11 refers to teammates and from 12 to 22 to opponents.
     */
    private int number = -1;
    /**
     * Each age of an encoded object (player or ball) is stored in this
     * attribute. Age-values reach from 0 to 2.
     */
    private int age = -1;
    /**
     * Decoded stamina values are stored in this value.
     */
    private int stamina = -1;
    /**
     * The decoded amount of cycles needed to intercept the ball is stored in
     * this attribute.
     */
    private int cycles = 0;
    /**
     * Each decoded body-angle of a player is stored here.
     */
    private double bodyAngle = 0;
    /**
     * Each decoded recovery-value of a player is stored here. Note that
     * this has a minimum of 0.5 and a maximum of 1.
     */
    private double recovery = 1.0;
    /**
     * This array is used to store the decoded player types of the opponents.
     */
    private int[] intArray;

    /**
     * Constructor - this class is able to encode and decode certain,
     * well-defined messages. It is optimized for performance, hence
     * pre-calculations of <code>valuesOfChars</code>, <code>sharedFrontValue
     * </code> and <code>sharedBackValue</code> are done here.
     * Additionally, the length of all defined messages have to be set to
     * <code>lengthOfMessage</code>. If this class is extended to support new
     * message types, their lengths have to be added here as well.
     */
    public MessageFactory() {
        // init all to -1
        Arrays.fill(this.valuesOfChars, -1);
        Arrays.fill(this.lengthOfMessage, -1);
        for (int[] a : this.sharedFrontValue) {
            Arrays.fill(a, -1);
        }
        for (int[] a : this.sharedBackValue) {
            Arrays.fill(a, -1);
        }

        // fill data of helping arrays
        for (int i = 0; i < NumberConverter.digits.length; i++) {
            this.valuesOfChars[NumberConverter.digits[i]] = i;
            for (int j = 0; j < this.sharedFrontValue[i].length; j++) {
                this.sharedFrontValue[NumberConverter.digits[i]][j] = i / (j + 1);
                this.sharedBackValue[NumberConverter.digits[i]][j] = i % (j + 1);
            }
        }

        // fill data of message-length information
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.PLAYER_POS.ordinal()]] = 4;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.PLAYER_POS_STAMINA.ordinal()]] = 4;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.GOALIE_POS.ordinal()]] = 4;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.PLAYER_SPD.ordinal()]] = 4;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.BALL_POS.ordinal()]] = 4;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.BALL_SPD.ordinal()]] = 4;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_PLAYER_POS.ordinal()]] = 5;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_PLAYER_SPD.ordinal()]] = 5;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_BALL_POS.ordinal()]] = 5;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_BALL_SPD.ordinal()]] = 5;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.STAMINA.ordinal()]] = 1;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.RECOVERY.ordinal()]] = 3;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.KILL_PLAYER.ordinal()]] = 1;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.COMPLETE_BALL.ordinal()]] = 9;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.KICKED_BALL.ordinal()]] = 9;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.DRIBBLE_BALL.ordinal()]] = 9;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.DRIBBLE_PPOS.ordinal()]] = 9;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.INTERCEPT_BALL.ordinal()]] = 9;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.COMPLETE_PLAYER.ordinal()]] = 9;
        this.lengthOfMessage[NumberConverter.digits[MESSAGES.TRAINER_OPPTYPES.ordinal()]] = 11;
    // to be continued ...
    }

    /**
     * This enumeration contains all possible messages that can be sent and
     * understood by the agents. The Messages have a specific length, an id,
     * which is the first character, and a content. Some messages are only IDs
     * (length 1).
     */
    public enum MESSAGES {

        MESSAGE_1, MESSAGE_2, MESSAGE_3, MESSAGE_4, MESSAGE_5, MESSAGE_6, MESSAGE_7, MESSAGE_8, MESSAGE_9, MESSAGE_10, MESSAGE_11, MESSAGE_12, PLAYER_POS_STAMINA, COMPLETE_BALL, PLAYER_POS, PLAYER_SPD, BALL_POS, BALL_SPD, STAMINA, KILL_PLAYER, TRAINER_PLAYER_POS, TRAINER_PLAYER_SPD, TRAINER_BALL_POS, TRAINER_BALL_SPD, INTERCEPT_BALL, COMPLETE_PLAYER, KICKED_BALL, DRIBBLE_BALL, GOALIE_POS, RECOVERY, TRAINER_OPPTYPES, DRIBBLE_PPOS
    }

    /**
     * A Players Position is encoded in the following way: 1. The first
     * character contains the id as usual 2. The first character contains the
     * type (which player) and an age information (0,1,2). 3. The next three
     * chars contain the position, with an average error of 15cm. To reduce to
     * 1cm, 4 chars should be used!
     *
     * @param player - the player, of which the position should be encoded
     * @param age - the age of the information (i.e. the time, how long the
     * player wasn't seen)
     * @return String - the message representing the players position
     */
    public String encodePlayerPosition(Player player, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = player.getPosition();
        int num = player.getNumber();
        if (num < 1) {
            return null;
        }
        if (num > 11) {
            return null;
        }
        if (!player.isFriend()) {
            num += 11;
        }

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.PLAYER_POS));

        // 2. player num and age
        this.str.append(NumberConverter.digits[(3 * num) + age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = Math.min(angle / this.maxAngle, 1);
        this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));
        return this.str.toString();
    }

    /**
     * This method encodes the player only in case, if it is the self player,
     * because then the sender is known (inside the message). Hence its number
     * and its age can be removed (because the number of the sender is
     * tranmitted as well, and the age is 0 per default), and stamina-
     * information can be added.
     *
     * @param player - the player, which should be self!
     * @return - the string containing the message
     */
    public String encodePlayerPositionAndStamina(Player player) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = player.getPosition();
        int num = player.getNumber();
        if (num < 1) {
            return null;
        }
        if (num > 11) {
            return null;
        }

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.PLAYER_POS_STAMINA));

        // 2. stamina: sender says only own stamina
        this.data_1[0] = player.getStamina() / (4000. + player.getPConf().EXTRA_STAMINA);
        if (this.data_1[0] > 1) {
            this.data_1[0] = 1;
        }
        this.str.append(NumberConverter.convertToChars(this.data_1, 1));

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = Math.min(angle / this.maxAngle, 1);
        this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));
        return this.str.toString();
    }

    /**
     * A Goalies Position is encoded in the following way: 1. The first
     * character contains the id as usual 2. The first character contains the
     * type (which player) and an age information (0,1,2). 3. The next three
     * chars contain the position, with an average error of 15cm. To reduce to
     * 1cm, 4 chars should be used!
     *
     * @param player - the goalie player
     * @param age - the age of this information (0, 1 or 2)
     * @return the string containing the message
     */
    public String encodeGoaliePosition(Player player, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = player.getPosition();
        int num = player.getNumber();
        if (num < 1) {
            return null;
        }
        if (num > 11) {
            return null;
        }
        if (!player.isFriend()) {
            num += 11;
        }

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.GOALIE_POS));

        // 2. player num and age
        this.str.append(NumberConverter.digits[(3 * num) + age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = Math.min(angle / this.maxAngle, 1);
        this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));
        return this.str.toString();
    }

    /**
     * A Ball Position is encoded in the following way: 1. The first character
     * contains the id as usual 2. The first character contains the age
     * information (0, .. 10). 3. The next three chars contain the position,
     * with an average error of 15cm. To reduce to 1cm, 4 chars should be used!
     *
     * @param the
     *            ball
     * @param the age of this information
     * @return the string containing the message
     */
    public String encodeBallPosition(Ball ball, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = ball.getPosition();
        age = Math.min(age, 10);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.BALL_POS));

        // age
        this.str.append(NumberConverter.digits[age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));

        return this.str.toString();
    }

    /**
     * This method encodes a ball with position, speed, and age
     *
     * @param ball - the ball
     * @param age - the age of this information
     * @return the String representation
     */
    public String encodeCompleteBall(Ball ball, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor position = ball.getPosition();
        Vektor speed = ball.getSpeed();
        age = Math.min(age, 20);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.COMPLETE_BALL));

        // 2. age
        this.str.append(NumberConverter.digits[age]);

        // 3.-6. position
        double angle = Vektor.CENTER.getAngleTo(position);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(position);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        // 7.-10. speed
        angle = speed.getAngle();
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        dist = speed.getLength();
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = Math.min(1, dist / this.maxBallSpdDist);
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));
        return this.str.toString();
    }

    /**
     * This method decodes a message which is generated with the
     * <code>encodeCompleteBall</code> method.
     * The age is set to <code>this.age</code>, the position and the
     * speed are set to <code>this.position</code> and <code>this.speed</code>.
     *
     * @param str -
     *            the encoded string
     * @return true, if decoding was successful
     */
    public boolean decodeCompleteBall(String str) {
        if (!isIdOfMessageType(MESSAGES.COMPLETE_BALL, str.charAt(0))) {
            return false;
        }
        this.age = this.valuesOfChars[str.charAt(1)];

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);

        // get speed
        this.cdata_4[0] = str.charAt(6);
        this.cdata_4[1] = str.charAt(7);
        this.cdata_4[2] = str.charAt(8);
        this.cdata_4[3] = str.charAt(9);

        result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
        angle = result[0] * this.maxAngle;
        dist = result[1] * this.maxBallSpdDist;
        this.speed.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method encodes a balls speed and a players position, indicating that
     * the player is dribbling. The age indicates weather we dribble (next
     * cycle) or someone else (current cycle). Should reach maximal a value of
     * 2!
     *
     * @param ball - the ball
     * @param player -
     *            friend or enemy
     * @param age -
     *            [0,2]
     * @return the String representation
     */
    public String encodeDribbleBall(Ball ball, Player player, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor position = ball.getPosition();
        Vektor speed = player.getSpeed();
        int number = player.getNumber();
        if (number < 1) {
            return null;
        }
        if (number > 11) {
            return null;
        }
        if (!player.isFriend()) {
            number += 11;
        }
        if (speed.getLength() < 0.1) {
            speed = new Vektor(Vektor.POLAR, 0.1, player.getBodyDir());
        }
        age = Math.max(0, age);
        age = Math.min(2, age);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.DRIBBLE_BALL));
        // 2. player num and age
        this.str.append(NumberConverter.digits[(3 * number) + age]);

        // 3.-6. position
        double angle = Vektor.CENTER.getAngleTo(position);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(position);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        // 7.-10. speed
        angle = speed.getAngle();
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        dist = speed.getLength();
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = Math.min(1, dist / this.maxPlayerSpdDist);
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));
        return this.str.toString();
    }

    /**
     * This method decodes the position of a player, and the speed of a ball,
     * which is used in the context of dribbling! The age is set as usual to
     * <code>this.age</code>, the position and the speed to <code>this.position
     * </code> and <code>this.speed</code>.
     *
     * @param str -
     *            the encoded string
     * @return true, if encoding was successful
     */
    public boolean decodeDribbleBall(String str) {
        if (!isIdOfMessageType(MESSAGES.DRIBBLE_BALL, str.charAt(0))) {
            return false;
        }

        // get number of player and age (for both):
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);

        // get speed
        this.cdata_4[0] = str.charAt(6);
        this.cdata_4[1] = str.charAt(7);
        this.cdata_4[2] = str.charAt(8);
        this.cdata_4[3] = str.charAt(9);

        result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
        angle = result[0] * this.maxAngle;
        dist = result[1] * this.maxPlayerSpdDist;
        this.speed.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method encodes the positions of two players, whereas the age refers
     * to that of the first player.
     *
     * @param player1 -
     *            friend or enemy
     * @param player2 -
     *            friend or enemy
     * @param age -
     *            [0,2]
     * @return the String representation
     */
    public String encodeDribblePlayer(Player player1, Player player2, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor position1 = player1.getPosition();
        Vektor position2 = player2.getPosition();
        int number1 = player1.getNumber();
        int number2 = player2.getNumber();
        if (number1 < 1) {
            return null;
        }
        if (number1 > 11) {
            return null;
        }
        if (!player1.isFriend()) {
            number1 += 11;
        }
        if (number2 < 1) {
            return null;
        }
        if (number2 > 11) {
            return null;
        }
        if (!player2.isFriend()) {
            number2 += 11;
        }

        age = Math.max(0, age);
        age = Math.min(2, age);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.DRIBBLE_PPOS));
        // 2. player1 num and age
        this.str.append(NumberConverter.digits[(3 * number1) + age]);

        // 3.-6. position of player 1
        double angle = Vektor.CENTER.getAngleTo(position1);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(position1);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        // 7. number of player 2
        this.str.append(NumberConverter.digits[number2]);

        // 8.-10. position of player 2
        angle = Vektor.CENTER.getAngleTo(position2);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        dist = Vektor.CENTER.getDistance(position2);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));
        return this.str.toString();
    }

    /**
     * This method decodes the position of two players which is used in the
     * context of dribbling! The age is set as usually to <code>this.age</code>,
     * the two positions to the corresponding elements of <code>this.positions
     * </code>. The numbers of the players are stored in the corresponding
     * elements of <code>this.numbers</code>.
     *
     * @param str -
     *            the encoded string for this message
     * @return true, if succeeds The positions are stored within the
     *         <positions>-array The numbers are stored within the
     *         <numbers>-array
     */
    public boolean decodeDribblePlayer(String str) {
        if (!isIdOfMessageType(MESSAGES.DRIBBLE_PPOS, str.charAt(0))) {
            return false;
        }

        // get number of player and age (for both):
        this.numbers[0] = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position of player 1
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.positions[0].pointAtPolar(dist, angle);

        this.numbers[1] = this.valuesOfChars[str.charAt(6)];

        // get position of player 2
        this.cdata_3[0] = str.charAt(7);
        this.cdata_3[1] = str.charAt(8);
        this.cdata_3[2] = str.charAt(9);

        result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
        angle = result[0] * this.maxAngle;
        dist = result[1] * this.maxPosDist;
        this.positions[1].pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method encodes a ball with position and speed which results from a
     * kicked ball!
     *
     * @param ball - the next ball, as it is expected after a kick
     * @return the String representation
     */
    public String encodeKickedBall(Ball ball) {
        this.str = new StringBuffer();

        // data to encode
        Vektor position = ball.getPosition();
        Vektor speed = ball.getSpeed();
        age = Math.min(age, 20);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.KICKED_BALL));

        // 2. age, always 1
        this.str.append(NumberConverter.digits[1]);

        // 3.-6. position
        double angle = Vektor.CENTER.getAngleTo(position);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(position);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        // 7.-10. speed
        angle = speed.getAngle();
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        dist = speed.getLength();
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = Math.min(1, dist / this.maxBallSpdDist);
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));
        return this.str.toString();
    }

    /**
     * This method decodes a message containing the new speed after a kick!
     * The age of the ball is set to <code>this.age</code>, its position and
     * speed as usual in <code>this.position</code> and <code>this.speed</code>.
     * @param str -
     *            the string-value!
     * @return true, if decoding was successful
     */
    public boolean decodeKickedBall(String str) {
        if (!isIdOfMessageType(MESSAGES.KICKED_BALL, str.charAt(0))) {
            return false;
        }
        this.age = this.valuesOfChars[str.charAt(1)];

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);

        // get speed
        this.cdata_4[0] = str.charAt(6);
        this.cdata_4[1] = str.charAt(7);
        this.cdata_4[2] = str.charAt(8);
        this.cdata_4[3] = str.charAt(9);

        result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4, 2);
        angle = result[0] * this.maxAngle;
        dist = result[1] * this.maxBallSpdDist;
        this.speed.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method encodes a player with position, speed, and age.
     *
     * @param player - the player to encode
     * @param age - the age of the player [0,1,2]
     * @return the String representation
     */
    public String encodeCompletePlayer(Player player, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor position = player.getPosition();
        Vektor speed = player.getSpeed();
        double bAngle = player.getBodyDir() + 180;
        int num = player.getNumber();
        if (num < 1) {
            return null;
        }
        if (num > 11) {
            return null;
        }
        if (!player.isFriend()) {
            num += 11;
        }
        age = Math.max(0, age);
        age = Math.min(2, age);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.COMPLETE_PLAYER));

        // 2. player num and age
        this.str.append(NumberConverter.digits[(3 * num) + age]);

        // 3.-6. position
        double angle = Vektor.CENTER.getAngleTo(position);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(position);
        this.data_2[0] = Math.min(angle / this.maxAngle, 1);
        this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        // 7.-9. speed
        angle = speed.getAngle();
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        dist = speed.getLength();
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = Math.min(1, dist / this.maxPlayerSpdDist);
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));

        // 10. body angle
        this.data_1[0] = (bAngle / this.maxAngle);
        // System.out.println("B-ANGLE: " + data_1[0] + " " + bAngle);
        this.str.append(NumberConverter.convertToChars(this.data_1, 1));

        return this.str.toString();
    }

    /**
     * This method decodes the position of a player, i.e. his number, age, his
     * position and his speed as absolute vektor! The values are stored in
     * <code>this.age</code>, <code>this.number</code>, <code>this.position</code>
     * and <code>this.speed</code> respectively.
     *
     * @param str -
     *            the encoded string-value!
     * @return true, if decoding was successful
     */
    public boolean decodeCompletePlayer(String str) {
        if (!isIdOfMessageType(MESSAGES.COMPLETE_PLAYER, str.charAt(0))) {
            return false;
        }

        // get number and age:
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);

        // get speed
        this.cdata_3[0] = str.charAt(6);
        this.cdata_3[1] = str.charAt(7);
        this.cdata_3[2] = str.charAt(8);

        result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
        angle = result[0] * this.maxAngle;
        dist = result[1] * this.maxPlayerSpdDist;
        this.speed.pointAtPolar(dist, angle);

        // get body angle
        this.cdata_1[0] = str.charAt(9);
        result = NumberConverter.convertToDoubles(this.cdata_1, 0, 1, 1);
        this.bodyAngle = (result[0] * this.maxAngle) - 180;

        return true;
    }

    /**
     * This method encodes the cycles that a player needs to intercept the ball,
     * together with the ball and its age.
     *
     * @param ball - the ball
     * @param the age of the ball
     * @param cycles - the number of cycles to intercept the ball
     * @return the encoded string for this message
     */
    public String encodeInterceptBall(Ball ball, int age, int cycles) {
        this.str = new StringBuffer();

        // data to encode
        Vektor position = ball.getPosition();
        Vektor speed = ball.getSpeed();
        age = Math.min(age, 20);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.INTERCEPT_BALL));

        // 2. age
        this.str.append(NumberConverter.digits[age]);

        // 3. cycles
        this.str.append(NumberConverter.digits[cycles]);

        // 4.-7. position
        double angle = Vektor.CENTER.getAngleTo(position);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(position);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        // 8.-10. speed
        angle = speed.getAngle();
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        dist = speed.getLength();
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = Math.min(1, dist / this.maxBallSpdDist);
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));
        return this.str.toString();
    }

    /**
     * This method decodes a message, which was generated using <code>
     * encodeInterceptBall()</code>. The age of the ball, the cycles for
     * intercepting, the position and the speed of the ball are set to <code>
     * this.age</code>, <code>this.cycles</code>, <code>this.position</code> and
     * <code>this.speed</code> respectively.
     *
     * @param the string representation of the message
     * @return true, if decoding was successful
     */
    public boolean decodeInterceptBall(String str) {
        if (!isIdOfMessageType(MESSAGES.INTERCEPT_BALL, str.charAt(0))) {
            return false;
        }
        this.age = this.valuesOfChars[str.charAt(1)];
        this.cycles = this.valuesOfChars[str.charAt(2)];
        // get position
        this.cdata_4[0] = str.charAt(3);
        this.cdata_4[1] = str.charAt(4);
        this.cdata_4[2] = str.charAt(5);
        this.cdata_4[3] = str.charAt(6);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);

        // get speed
        this.cdata_3[0] = str.charAt(7);
        this.cdata_3[1] = str.charAt(8);
        this.cdata_3[2] = str.charAt(9);

        result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
        angle = result[0] * this.maxAngle;
        dist = result[1] * this.maxBallSpdDist;
        this.speed.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * A Players Position is encoded in the following way: 1. The first
     * character contains the id as usual 2. The first character contains the
     * type (which player) and an age information (0,1,2). 3. The next four
     * chars contain the position, with an average error of 1 cm!
     *
     * @param player - the player, whose state is encoded
     * @return the string representation of the message
     */
    public String encodeTrainerPlayerPosition(Player player) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = player.getPosition();
        int num = player.getNumber();
        if (num < 1) {
            return null;
        }
        if (num > 11) {
            return null;
        }
        if (!player.isFriend()) {
            num += 11;
        }
        int age = 0;

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_PLAYER_POS));

        // 2. player num and age
        this.str.append(NumberConverter.digits[(3 * num) + age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = Math.min(angle / this.maxAngle, 1);
        this.data_2[1] = Math.min(dist / this.maxPosDist, 1);
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        return this.str.toString();
    }

    /**
     * A Ball Position is encoded in the following way: 1. The first character
     * contains the id as usual 2. The first character contains the type (0) and
     * an age information (0,1,2). 3. The next four chars contain the position,
     * with an average error of 1cm!
     *
     * @param the
     *            ball
     * @return - the string representation of this message
     */
    public String encodeTrainerBallPosition(Ball ball) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = ball.getPosition();
        int age = 0;

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_BALL_POS));

        // 2. player num and age
        this.str.append(NumberConverter.digits[age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPosDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        return this.str.toString();
    }

    /**
     * This message encodes the stamina of a player!
     *
     * @param stam -
     *            the given stamina value!
     * @return the string representation!
     */
    public String encodeStamina(int stam) {
        this.str = new StringBuffer();
        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.STAMINA));
        this.str.append(convertIntToCharacter(stam, 0, this.maxStamina));
        return this.str.toString();
    }

    /**
     * This message decodes the stamina value! The result is set to <code>
     * this.stamina</code>
     *
     * @param str -
     *            the encoded stamina!
     * @return the stamina value
     */
    public boolean decodeStamina(String str) {
        if (!isIdOfMessageType(MESSAGES.STAMINA, str.charAt(0))) {
            return false;
        }
        this.cdata_1[0] = str.charAt(1);
        double[] result = NumberConverter.convertToDoubles(this.cdata_1, 0, 1,
                1);
        this.stamina = (int) (result[0] * this.maxStamina);

        return true;
    }

    /**
     * This message encodes the recovery of a player!
     *
     * @param effort -
     *            the given recovery value!
     * @return the string representation!
     */
    public String encodeRecovery(int num, double rec) {
        this.str = new StringBuffer();
        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.RECOVERY));
        this.str.append(NumberConverter.digits[(3 * num)]);
        this.str.append(convertDoubleToCharsHP(rec, 0.5, 1.0));
        return this.str.toString();
    }

    /**
     * This message decodes the recovery value! The result is set to <code>
     * this.recovery</code>.
     *
     * @param str -
     *            the encoded recovery!
     * @return the recovery value
     */
    public boolean decodeRecovery(String str) {
        if (!isIdOfMessageType(MESSAGES.RECOVERY, str.charAt(0))) {
            return false;
        }
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.cdata_2[0] = str.charAt(2);
        this.cdata_2[1] = str.charAt(3);
        double[] result = NumberConverter.convertToDoubles(this.cdata_2, 0, 2,
                1);
        this.recovery = 0.5 + (result[0] * (1.0 - 0.5));

        return true;
    }

    /**
     * This message encodes a kill-player command, which is defined by the
     * number of the corresponding player. This may be used to set players
     * to non-existant.
     *
     * @param num - the number of the killed player [1..22], whereas values
     * from 12 to 22 refer to opponents.
     *
     * @return the string representation!
     */
    public String encodeKillPlayer(int num) {
        if (num > 22 || num < 1) {
            return "";
        }

        this.str = new StringBuffer();
        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.KILL_PLAYER));

        // add data (1 chars)
        this.str.append(NumberConverter.digits[num]);
        return this.str.toString();
    }

    /**
     * This message decodes a kill player message, received from the trainer.
     * The corresponding result is set to <code>this.number</code>,  whereas
     * values from 12 to 22 refer to opponents.
     *
     * @return true, if the decoding was successful
     */
    public boolean decodeKillPlayer(String str) {
        if (!isIdOfMessageType(MESSAGES.KILL_PLAYER, str.charAt(0))) {
            return false;
        }
        this.number = this.valuesOfChars[str.charAt(1)];
        return true;
    }

    /**
     * This method decodes the position of a player, i.e. his number, his age
     * and his position as absolute vektor! The results are set to <code>
     * this.number</code>, <code>this.age</code> and <code>this.position</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if the decoding was successful
     */
    public boolean decodePlayerPosString(String str) {
        if (!isIdOfMessageType(MESSAGES.PLAYER_POS, str.charAt(0))) {
            return false;
        }

        // get number and age:
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_3[0] = str.charAt(2);
        this.cdata_3[1] = str.charAt(3);
        this.cdata_3[2] = str.charAt(4);

        double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method decodes a players position and his stamina. Note that the id
     * of the player is given in the info-type of this message, because this is
     * always the sender. The results are set to <code>this.stamina</code> and
     * </code>this.position</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if decoding was successful
     */
    public boolean decodePlayerPosAndStaminaString(String str) {
        if (!isIdOfMessageType(MESSAGES.PLAYER_POS_STAMINA, str.charAt(0))) {
            return false;
        }

        // get number and age:
        this.cdata_1[0] = str.charAt(1);
        double[] result = NumberConverter.convertToDoubles(this.cdata_1, 0, 1,
                1);
        this.stamina = (int) (result[0] * 4000);

        // get position
        this.cdata_3[0] = str.charAt(2);
        this.cdata_3[1] = str.charAt(3);
        this.cdata_3[2] = str.charAt(4);

        result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3, 2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method decodes the position of a goalie.The results are set to <code>
     * this.number</code>, <code>this.age</code> and <code>this.position</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if decoding was successful
     */
    public boolean decodeGoaliePosString(String str) {
        if (!isIdOfMessageType(MESSAGES.GOALIE_POS, str.charAt(0))) {
            return false;
        }

        // get number and age:
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_3[0] = str.charAt(2);
        this.cdata_3[1] = str.charAt(3);
        this.cdata_3[2] = str.charAt(4);

        double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method decodes the position of the ball. The results are set to
     * <code>this.age</code> and <code>this.position</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if decoding was successful
     */
    public boolean decodeBallPosString(String str) {
        if (!isIdOfMessageType(MESSAGES.BALL_POS, str.charAt(0))) {
            return false;
        }
        // age & number
        // age = sharedBackValue[str.charAt(1)][2];
        // the ball only has age, no number
        this.age = this.valuesOfChars[str.charAt(1)];

        // get position
        this.cdata_3[0] = str.charAt(2);
        this.cdata_3[1] = str.charAt(3);
        this.cdata_3[2] = str.charAt(4);

        double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method decodes the position of a player, which is given with a
     * higher precision. The results are set to <code>
     * this.number</code>, <code>this.age</code> and <code>this.position</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if the decoding was successful.
     */
    public boolean decodeTrainerPlayerPosString(String str) {
        if (!isIdOfMessageType(MESSAGES.TRAINER_PLAYER_POS, str.charAt(0))) {
            return false;
        }

        // get number and age:
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * This method decodes the position of the ball, which was encoded with
     * higher precision. The results are set to <code>this.age</code> and
     * <code>this.position</code>.
     *
     * @param str -
     *            the string-value!
     * @return the position!
     */
    public boolean decodeTrainerBallPosString(String str) {
        if (!isIdOfMessageType(MESSAGES.TRAINER_BALL_POS, str.charAt(0))) {
            return false;
        }
        // age & number
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPosDist;
        this.position.pointAtPolar(dist, angle);
        return true;
    }

    /**
     * A Players Speed is encoded in the following way: 1. The first character
     * contains the id as usual 2. The first character contains the type (which
     * player) and an age information (0,1,2). 3. The next three chars contain
     * the speed.
     *
     * @param the
     *            player
     * @param age - the age of the information
     * @return the string representation
     */
    public String encodePlayerSpeed(Player player, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = player.getSpeed();
        int num = player.getNumber();
        if (num < 1) {
            return null;
        }
        if (num > 11) {
            return null;
        }
        if (!player.isFriend()) {
            num += 11;
        }

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.PLAYER_SPD));

        // 2. player num and age
        this.str.append(NumberConverter.digits[(3 * num) + age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPlayerSpdDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));

        return this.str.toString();
    }

    /**
     * A Ball Position is encoded in the following way: 1. The first character
     * contains the id as usual 2. The first character contains the type (0) and
     * an age information (0,1,2). 3. The next three chars contain the position,
     * with an average error of 15cm. To reduce to 1cm, 4 chars should be used!
     *
     * @param the
     *            ball
     * @param age - the age of the onformation
     * @return - the string representation
     */
    public String encodeBallSpeed(Ball ball, int age) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = ball.getSpeed();

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.BALL_SPD));

        // 2. player num and age
        this.str.append(NumberConverter.digits[age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = Math.min(dist / this.maxBallSpdDist, 1);
        this.str.append(NumberConverter.convertToChars(this.data_2, 3));

        return this.str.toString();
    }

    /**
     * A Players Speed is encoded in the following way: 1. The first character
     * contains the id as usual 2. The first character contains the type (which
     * player) and an age information (0,1,2). 3. The next four chars contain
     * the speed (high precision).
     *
     * @param the
     *            player
     * @return the string representation
     */
    public String encodeTrainerPlayerSpeed(Player player) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = player.getSpeed();
        int num = player.getNumber();
        if (num < 1) {
            return null;
        }
        if (num > 11) {
            return null;
        }
        if (!player.isFriend()) {
            num += 11;
        }
        int age = 0; // Math.min(player.getAge(0),2);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_PLAYER_SPD));

        // 2. player num and age
        this.str.append(NumberConverter.digits[(3 * num) + age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxPlayerSpdDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        return this.str.toString();
    }

    /**
     * A Ball Position is encoded in the following way: 1. The first character
     * contains the id as usual 2. The first character contains the type (0) and
     * an age information (0,1,2). 3. The next four chars contain the position,
     * with an average error of 1cm!
     *
     * @param the
     *            ball
     * @return - the string representation
     */
    public String encodeTrainerBallSpeed(Ball ball) {
        this.str = new StringBuffer();

        // data to encode
        Vektor v = ball.getSpeed();
        int age = 0; // Math.min(ball.getAge(0),2);

        // create data:
        // 1. id
        this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_BALL_SPD));

        // 2. player num and age
        this.str.append(NumberConverter.digits[age]);

        // 3. vektor
        double angle = Vektor.CENTER.getAngleTo(v);
        // convert angle to 360 variant
        if (angle < 0) {
            angle = 360 + angle;
        }
        double dist = Vektor.CENTER.getDistance(v);
        this.data_2[0] = angle / this.maxAngle;
        this.data_2[1] = dist / this.maxBallSpdDist;
        this.str.append(NumberConverter.convertToChars(this.data_2, 4));

        return this.str.toString();
    }

    /**
     * This method decodes the speed of a player. The results are set to <code>
     * this.number</code>, <code>this.age</code> and <code>this.speed</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if decoding was successful
     */
    public boolean decodePlayerSpdString(String str) {
        if (!isIdOfMessageType(MESSAGES.PLAYER_SPD, str.charAt(0))) {
            return false;
        }

        // get number and age:
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_3[0] = str.charAt(2);
        this.cdata_3[1] = str.charAt(3);
        this.cdata_3[2] = str.charAt(4);

        double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPlayerSpdDist;
        this.speed.pointAtPolar(dist, angle);

        return true;
    }

    /**
     * This method decodes the speed of the ball. The results are set to <code>
     * <code>this.age</code> and <code>this.speed</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if decoding was successful
     */
    public boolean decodeBallSpdString(String str) {
        if (!isIdOfMessageType(MESSAGES.BALL_SPD, str.charAt(0))) {
            return false;
        }
        this.age = this.sharedBackValue[str.charAt(1)][2];
        this.number = 0;

        // get position
        this.cdata_3[0] = str.charAt(2);
        this.cdata_3[1] = str.charAt(3);
        this.cdata_3[2] = str.charAt(4);

        double[] result = NumberConverter.convertToDoubles(this.cdata_3, 0, 3,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxBallSpdDist;
        this.speed.pointAtPolar(dist, angle);

        return true;
    }

    /**
     * This method decodes the speed of a player, which was encoded with higher
     * precision. The results are set to <code>this.number</code>, <code>
     * this.age</code> and <code>this.speed</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, if the decoding was successful
     */
    public boolean decodeTrainerPlayerSpdString(String str) {
        if (!isIdOfMessageType(MESSAGES.TRAINER_PLAYER_SPD, str.charAt(0))) {
            return false;
        }

        // get number and age:
        this.number = this.sharedFrontValue[str.charAt(1)][2];
        this.age = this.sharedBackValue[str.charAt(1)][2];

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxPlayerSpdDist;
        this.speed.pointAtPolar(dist, angle);

        return true;
    }

    /**
     * This method decodes the speed of the ball, which was encoded with high
     * precision. The results are set to <code>this.age</code> and
     * <code>this.speed</code>.
     *
     * @param str -
     *            the string-value!
     * @return true, fi the decoding was successful
     */
    public boolean decodeTrainerBallSpdString(String str) {
        if (!isIdOfMessageType(MESSAGES.TRAINER_BALL_SPD, str.charAt(0))) {
            return false;
        }
        this.age = this.sharedBackValue[str.charAt(1)][2];
        this.number = 0;

        // get position
        this.cdata_4[0] = str.charAt(2);
        this.cdata_4[1] = str.charAt(3);
        this.cdata_4[2] = str.charAt(4);
        this.cdata_4[3] = str.charAt(5);

        double[] result = NumberConverter.convertToDoubles(this.cdata_4, 0, 4,
                2);
        double angle = result[0] * this.maxAngle;
        double dist = result[1] * this.maxBallSpdDist;
        this.speed.pointAtPolar(dist, angle);

        return true;
    }

    /**
     * This method encodes the opponent types estimated by the trainer into a
     * string!
     *
     * @param types -
     *            the type-values!
     * @return the string representation
     */
    public String encodeOpponentTypes(int[] types) {
        str = new StringBuffer();
        this.str.append(this.getIdForMessageType(MESSAGES.TRAINER_OPPTYPES));
        for (int i = 0; i < types.length; i++) {
            str.append(NumberConverter.digits[types[i]]);
        }
        return str.toString();
    }

    /**
     * This message decodes the player-types of the oppponents. The result is
     * set to the array <code>this.intArray</code>.
     *
     * @param str - the string representation of this message
     * @return true, if decoding was successful.
     */
    public boolean decodeOpponentTypes(String str) {
        if (!isIdOfMessageType(MESSAGES.TRAINER_OPPTYPES, str.charAt(0))) {
            return false;
        }
        intArray = new int[str.length() - 1];
        for (int i = 1; i < str.length(); i++) {
            intArray[i - 1] = this.valuesOfChars[str.charAt(i)];
        }
        return true;
    }

    /**
     * This method converts an integer to a character of the given char-alphabet
     * (NumberConverter). The higher the maxValue, the greater is the loss of
     * precision.
     *
     * @param value -
     *            the value - has to be between min- and maxValue
     * @param minValue -
     *            the max-value
     * @param maxValue -
     *            the max-value
     * @return the corresponding character
     */
    private char convertIntToCharacter(int value, int minValue, int maxValue) {
        value = Math.max(value, minValue);
        value = Math.min(value, maxValue);
        double range = maxValue - minValue;
        double v = value;
        v = v / range;
        this.data_1[0] = v;
        return (NumberConverter.convertToChars(data_1, 1))[0];
    }

    /**
     * This method converts a double to 2 characters of the given char-alphabet
     * (NumberConverter). The higher the maxValue, the greater is the loss of
     * precision.
     *
     * @param value -
     *            the value - has to be between min- and maxValue
     * @param minValue -
     *            the max-value
     * @param maxValue -
     *            the max-value
     * @return the corresponding characters
     */
    private char[] convertDoubleToCharsHP(double value, double minValue,
            double maxValue) {
        value = Math.max(value, minValue);
        value = Math.min(value, maxValue);
        double range = maxValue - minValue;
        value = (value - minValue) / range;
        data_1[0] = value;
        return (NumberConverter.convertToChars(data_1, 2));
    }

    /**
     * This method calculates the length of the message in characters. Note that
     * the length is excluding the id, that is, the total number of chars for
     * sending is +1. If the result is -1, the message is not defined!
     *
     * @param mess -
     *            the message type
     * @return the length!
     */
    public int getLengthOfMessage(MESSAGES mess) {
        if (mess == null) {
            return -1;
        }
        return lengthOfMessage[mess.ordinal()];
    }

    /**
     * This message tests, if the given message type corresponds to the given
     * character-id!
     *
     * @param mess -
     *            the message type
     * @param id -
     *            the id
     * @return true, if the message type belongs to the id
     */
    public boolean isIdOfMessageType(MESSAGES mess, char id) {
        if (mess == null) {
            return false;
        } else {
            return (getIdForMessageType(mess) == id);
        }
    }

    /**
     * This method retrieves the id of a giveb message-type!
     *
     * @param mess -
     *            the message type
     * @return the corresponding character!
     */
    public char getIdForMessageType(MESSAGES mess) {
        return NumberConverter.digits[mess.ordinal()];
    }

    /**
     * Method retrieves the age of the decoeded objects (ball or player). Use
     * only after decoding, else other values might be returned!
     *
     * @return the decoded age (0-2)
     */
    public int getAge() {
        return age;
    }

    /**
     * Method retrieves the number of an object, which can be used to determine
     * the correct one from worldmodel: 0 - ball 1-11 own-team, 12-22 other
     * team.
     *
     * @return the number, which belongs to a player
     */
    public int getNumber() {
        return number;
    }

    /**
     * Retrieves the decoded position of a ball or player
     *
     * @return the absolute vektor
     */
    public Vektor getPosition() {
        return position;
    }

    /**
     * Retrieves the absolute speed of a ball or player
     *
     * @return the absolute vektor
     */
    public Vektor getSpeed() {
        return speed;
    }

    /**
     * Retrieves the stamina of the sender
     *
     * @return the stamina value with a not so high precision.
     */
    public int getStamina() {
        return stamina;
    }

    /**
     * This method retrieves the cycles used for intercepting
     *
     * @return the number of cycles.
     */
    public int getCycles() {
        return cycles;
    }

    /**
     * Retrieves the recovery of the sender.
     *
     * @return the recovery value
     */
    public double getRecovery() {
        return recovery;
    }

    /**
     * Retrieves the body angle of a player
     *
     * @return the body angle of a player.
     */
    public double getBodyAngle() {
        return bodyAngle;
    }

    /**
     * @return Returns the numbers.
     */
    public int[] getNumbers() {
        return numbers;
    }

    /**
     * @return Returns the positions.
     */
    public Vektor[] getPositions() {
        return positions;
    }

    /**
     * This method extracts all messages that are inside a string by using the
     * length of each message and cutting the string into separated pieces!
     *
     * @param str - the complete received string, which may contain multiple
     * messages.
     * @param the current body-cycle, which is used to check whether a message
     * is received in the same cycle or before.
     */
    public void generateMessages(String str, int bodyCycle) {
        if (this.bodyCycle != bodyCycle || bodyCycle == -1) {
            messageTypes.clear();
            messageContent.clear();
            this.bodyCycle = bodyCycle;
        }
        for (int i = 0; i < str.length();) {
            char c = str.charAt(i);
            MESSAGES m = (MESSAGES.values())[valuesOfChars[c]];
            messageTypes.add(m);
            messageContent.add(str.substring(i, i + lengthOfMessage[c] + 1));
            i += lengthOfMessage[c] + 1;
        }
    }

    /**
     * This method retrieves all received messagy types in a list!
     *
     * @return all message-types, which were received!
     */
    public LinkedList<MESSAGES> getMessageTypes() {
        return messageTypes;
    }

    /**
     * This method encodes the message, which is part of the
     * messageContent-List!
     *
     * @param index -
     *            the inedex of the message!
     */
    public void decodeMessageNumber(int index) {
        if (index < 0) {
            return;
        }
        if (index >= messageContent.size()) {
            return;
        }
        MESSAGES mess = messageTypes.get(index);
        if (mess == MESSAGES.BALL_POS) {
            decodeBallPosString(messageContent.get(index));
        } else if (mess == MESSAGES.BALL_SPD) {
            decodeBallSpdString(messageContent.get(index));
        } else if (mess == MESSAGES.PLAYER_POS) {
            decodePlayerPosString(messageContent.get(index));
        } else if (mess == MESSAGES.PLAYER_POS_STAMINA) {
            decodePlayerPosAndStaminaString(messageContent.get(index));
        } else if (mess == MESSAGES.PLAYER_SPD) {
            decodePlayerSpdString(messageContent.get(index));
        } else if (mess == MESSAGES.STAMINA) {
            decodeStamina(messageContent.get(index));
        } else if (mess == MESSAGES.KILL_PLAYER) {
            decodeKillPlayer(messageContent.get(index));
        } else if (mess == MESSAGES.TRAINER_PLAYER_POS) {
            decodeTrainerPlayerPosString(messageContent.get(index));
        } else if (mess == MESSAGES.TRAINER_BALL_POS) {
            decodeTrainerBallPosString(messageContent.get(index));
        } else if (mess == MESSAGES.TRAINER_PLAYER_SPD) {
            decodeTrainerPlayerSpdString(messageContent.get(index));
        } else if (mess == MESSAGES.TRAINER_BALL_SPD) {
            decodeTrainerBallSpdString(messageContent.get(index));
        } else if (mess == MESSAGES.COMPLETE_BALL) {
            decodeCompleteBall(messageContent.get(index));
        } else if (mess == MESSAGES.INTERCEPT_BALL) {
            decodeInterceptBall(messageContent.get(index));
        } else if (mess == MESSAGES.COMPLETE_PLAYER) {
            decodeCompletePlayer(messageContent.get(index));
        } else if (mess == MESSAGES.KICKED_BALL) {
            decodeKickedBall(messageContent.get(index));
        } else if (mess == MESSAGES.DRIBBLE_BALL) {
            decodeDribbleBall(messageContent.get(index));
        } else if (mess == MESSAGES.GOALIE_POS) {
            decodeGoaliePosString(messageContent.get(index));
        } else if (mess == MESSAGES.RECOVERY) {
            decodeRecovery(messageContent.get(index));
        } else if (mess == MESSAGES.TRAINER_OPPTYPES) {
            decodeOpponentTypes(messageContent.get(index));
        } else if (mess == MESSAGES.DRIBBLE_PPOS) {
            decodeDribblePlayer(messageContent.get(index));
        }
    }

    /**
     * This return the array of values, in which the opponent types are stored.
     *
     * @return the array <code>this.intArray</code>.
     */
    public int[] getIntArray() {
        return intArray;
    }
}
